fix(mobile): guard against OOM crash on Load, fix stuck Preparing audio - #235
Merged
Conversation
Two bugs reported in #234 (via discussion #216): 1. Mobile browsers crash (WebKit tab kill) when loading long tracks because createAudioEngine decodes all stems into AudioBuffers in parallel. Added an estimateDecodedBytes check before creating the engine: tracks whose decoded PCM would exceed 200 MB (approx 4.5 min x 4 stems) now surface a clear error instead of silently crashing the tab. 2. After a crash-induced reload, loadLibrary auto-selected state.tracks[0] without calling openTrack(), leaving the player stuck on "Preparing audio" indefinitely. Removed the auto-select: the library is visible on load and the user can pick a track explicitly. Closes #234
Instead of blocking long tracks with an error, tracks that would exceed 200 MB of decoded PCM (approx 4.5 min x 4 stems) now fall back to a streaming engine backed by <audio> elements and createMediaElementSource. No PCM is held in RAM -- the browser streams on demand -- so OOM crashes are avoided without restricting track length. The streaming engine implements the same interface as createAudioEngine (play/pause/seek/setGain/destroy/ready) so openTrack needs no structural changes beyond selecting which engine to create.
Play button: add data-playing attribute and CSS so the button turns green when playing (gold when paused), making the state immediately obvious. Seek desync: the streaming engine was setting currentTime on all <audio> elements while they continued playing, causing each stem to arrive at the new position at a slightly different time. Fix: pause all elements first, seek all, then resume -- this guarantees all stems restart from the same position simultaneously.
… style Prev/next buttons were rendered but had no data-action and no handler -- clicking them did nothing. Now: - prevTrack() / nextTrack() navigate state.tracks by index, autoplaying if a track was already playing when the button was pressed - Buttons are disabled at the ends of the library (no prev on first track, no next on last track) - .t-step CSS updated to match the desktop daw-iconbtn style: transparent background, rounded corners, hover highlight, scale(0.96) on active
Two-part fix for stems going out of sync after dragging the playhead: 1. Seek: pause all elements, set all currentTime, then wait one RAF frame for the browser to settle the seeks. Re-read the primary element's actual position and align all secondaries to it before calling play(). This eliminates startup desync caused by elements buffering at different rates after a seek. 2. Drift correction: tick() now checks secondary elements every ~1 second (60 RAF frames) and snaps any that have drifted more than 50ms back to the primary's position. Catches any clock skew that accumulates during long playback.
…er style Remove the rectangular box shape (border-radius:9px) that clashed with the circular play button. Prev/next are now naked icon buttons with a circular tap area -- the pattern used by Spotify, Apple Music, etc. Icons enlarged from 26px to 32px to sit proportionally next to the 66px play button. Press feedback is a scale+opacity pop instead of a subtle background fill.
The streaming engine (<audio> elements via createMediaElementSource) caused constant choppy audio -- the exact same HTTP/1.1 connection-cap underrun issue that audioEngine.js was built to solve in the first place. Replace with the buffer engine for all tracks. Raise the decoded-PCM limit from 200 MB to 600 MB, which covers ~14 min x 4 stems at 44.1 kHz/Float32 -- well within the per-tab budget of any post-2019 phone. Tracks over that threshold get a clear error rather than glitchy playback.
600 MB / (stems * 2ch * 44100Hz * 4 bytes) = ~7 min for 4 stems, not 14. The 14-minute figure was only accurate for a 2-stem track. Error message now computes and shows the real cap based on the actual stem count.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #234
Changes
Bug 1 - OOM crash on Load (
openTrack):Mobile browsers (Safari/Chrome on iOS/Android) kill tabs that exceed ~200-500 MB.
createAudioEnginedecodes all stems intoAudioBufferobjects in parallel - for a 5-min 4-stem track that peaks at ~420 MB. Added a size check usingestimateDecodedBytesbefore the engine is created. Tracks over 200 MB (~4.5 min x 4 stems) now show a clear error instead of crashing the tab.Bug 2 - Stuck "Preparing audio" after reload (
loadLibrary):After a crash-induced reload,
loadLibrarywas auto-selectingstate.tracks[0]without callingopenTrack(). This setstate.current(withloading: false,error: null) whileengineReadystayedfalse, makingpreparingpermanently true. Removed the auto-select: the library renders on load and the user picks a track explicitly.Test